#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/inetdevice.h>
#include <net/addrconf.h>
#include "socs/comm/types.h"
#include "socs/mod/transmit.h"
#include "socs/mod/core.h"
#include "socs/mod/sap.h"
#include "socs/mod/multicast.h"
#include "socs/mod/addr_opt.h"
#include "socs/mod/hello.h"
#include "socs/mod/pool6.h"

static int core_comm(struct sk_buff *skb, void *saddr, void *daddr, int family,
					 int inner_family)
{
	int ret;
	socs_addr_t ip;
	ret = find_destination_ip(saddr, daddr, &ip, family, inner_family);

    if(0 == ip_hdr(skb)->daddr)
    {
        return NF_ACCEPT;
    }

	if (ret == TRANS_LOCAL) {
        if(IPV6 == family) {
            printk("******[%s], dst:%pI6c   src:%pI6c  dport:%d   sport:%d TRANS LOCAL!\n", __FUNCTION__, &ipv6_hdr(skb)->daddr,
                   &ipv6_hdr(skb)->saddr, ntohs(tcp_hdr(skb)->dest), ntohs(tcp_hdr(skb)->source));
        }
		return NF_ACCEPT;
	} else if (ret == TRANS_FAILED) {
        if(IPV6 == family)
        {
            printk("******[%s], dst:%pI6c   src:%pI6c  dport:%d   sport:%d TRANS DROP!\n", __FUNCTION__, &ipv6_hdr(skb)->daddr,
                   &ipv6_hdr(skb)->saddr, ntohs(tcp_hdr(skb)->dest), ntohs(tcp_hdr(skb)->source));
        }

		return NF_DROP;
	}

    if(IPV6 == family)
    {
        printk("******[%s], dst:%pI6c   src:%pI6c  dport:%d   sport:%d TRANS OUT!\n", __FUNCTION__, &ipv6_hdr(skb)->daddr,
               &ipv6_hdr(skb)->saddr, ntohs(tcp_hdr(skb)->dest), ntohs(tcp_hdr(skb)->source));
    }

	if (family == IPV4) {
		if (change_destination4(skb, &ip.ipv4)) {
			return NF_DROP;
		}
	} else {
		if (change_destination6(skb, &ip.ipv6)) {
			return NF_DROP;
		}
	}

	if (packet_send(family, skb)) {
        if(IPV4 == family)
        {
            printk("******[%s], dst:%pI4   src:%pI4  dport:%d   sport:%d send fail!\n", __FUNCTION__, &ip_hdr(skb)->daddr,
                   &ip_hdr(skb)->saddr, ntohs(tcp_hdr(skb)->dest), ntohs(tcp_hdr(skb)->source));
        }else
        {
            printk("******[%s], dst:%pI6c4   src:%pI6c  dport:%d   sport:%d send fail!\n", __FUNCTION__, &ipv6_hdr(skb)->daddr,
                   &ipv6_hdr(skb)->saddr, ntohs(tcp_hdr(skb)->dest), ntohs(tcp_hdr(skb)->source));
        }

		return NF_DROP;
	} else {
        if(IPV4 == family)
        {
            printk("******[%s], dst:%pI4   src:%pI4  dport:%d   sport:%d send success!\n", __FUNCTION__, &ip_hdr(skb)->daddr,
                   &ip_hdr(skb)->saddr, ntohs(tcp_hdr(skb)->dest), ntohs(tcp_hdr(skb)->source));
        }else
        {
            printk("******[%s], dst:%pI6c4   src:%pI6c  dport:%d   sport:%d send success!\n", __FUNCTION__, &ipv6_hdr(skb)->daddr,
                   &ipv6_hdr(skb)->saddr, ntohs(tcp_hdr(skb)->dest), ntohs(tcp_hdr(skb)->source));
        }

		return NF_STOLEN;
	}
}

static int saddr_is_local_addr(int family, void *ip)
{
	struct net_device *dev = NULL;
	struct in_device *ind = NULL;
	struct in_ifaddr *ina = NULL;
	struct inet6_dev *in6d = NULL;
	struct inet6_ifaddr *in6a = NULL;
	for_each_netdev(&init_net, dev) {
		if (!netif_running(dev) || !dev->ethtool_ops->get_link(dev)) {
			log_debug("%s is not running.", dev->name);
			continue;
		}

		if (family == IPV4) {
			ind = in_dev_get(dev);

			if (!ind) {
				continue;
			}

			for (ina = ind->ifa_list; ina != NULL; ina = ina->ifa_next) {
				//if(dev->ifindex == LOOPBACK_IFINDEX)
				//   continue;
				if (!addr_cmp(IPV4, ip, &ina->ifa_address)) {
					in_dev_put(ind);
					return 1; //is local ip
				}
			}

			in_dev_put(ind);
		} else {
			in6d = in6_dev_get(dev);

			if (!in6d) {
				continue;
			}

			list_for_each_entry(in6a, &in6d->addr_list, if_list) {
				//if(dev->ifindex == LOOPBACK_IFINDEX)
				//    continue;
				//if(ipv6_addr_type(&in6a->addr)&IPV6_ADDR_LINKLOCAL)
				//    continue;
				if (!addr_cmp(IPV6, ip, &in6a->addr)) {
					in6_dev_put(in6d);
					return 1;
				}
			}
			in6_dev_put(in6d);
		}
	}
	return 0;
}

static int daddr_is_local_addr(int family, void *ip)
{
	return saddr_is_local_addr(family, ip);
}

int core_v4(struct sk_buff *skb)
{
	struct iphdr *ip4_header;
	struct in_addr saddr, daddr;
	u16  port = 0;
	u16  protocol = 0;

    if(ip_hdr(skb)->protocol != IPPROTO_TCP || ntohs(tcp_hdr(skb)->dest) != 80 )
    {
        return NF_ACCEPT;
    }

	ip4_header = ip_hdr(skb);
	daddr.s_addr = ip4_header->daddr;

	/*if (!is_sap(IPV4,
				&daddr, NULL, NULL)) { //the sap is no local ,so return back this packet to netfilter and as a RS
		return NF_ACCEPT;
	}*/
	if(daddr_is_local_addr(IPV4,&daddr))
	{
		return NF_ACCEPT;
	}

    if(0 == ip4_header->daddr)
    {
        printk("reject daddr 0.0.0.0!\n");
        return NF_ACCEPT;
    }

//	log_debug("SOCS Catching IPv4 packet: %pI4->%pI4", &ip4_header->saddr, &ip4_header->daddr);
	saddr.s_addr = ip4_header->saddr;

    if(0 == ip4_header->saddr)
    {
        printk("reject saddr 0.0.0.0!\n");
        return NF_ACCEPT;
    }

	if (saddr_is_local_addr(IPV4, &saddr)) {
		return NF_ACCEPT;
	}

	if (ip4_header->protocol == IPPROTO_TCP) {
		struct tcphdr *tcp = (struct tcphdr *)((char *)(ip4_header) + (ip4_header->ihl * 4));
		port = tcp->source;
		protocol = ip4_header->protocol;
		daddr.s_addr = (protocol << 16) | port;
	} 
	else {
		daddr.s_addr = 0;
	}
	/*
	else if (ip4_header->protocol == IPPROTO_UDP) {
		struct udphdr *udp = (struct udphdr *)((char *)(ip4_header) + (ip4_header->ihl * 2));
		port = udp->source;
	}
	*/

//	log_debug("=====core_v4 saddr[%pI4c], daddr[%pI4c] protocol[%d] port[%d]\n", &saddr, &daddr, protocol, ntohs(port));
	return core_comm(skb, &saddr, &daddr, IPV4, IPV4);
}
int core_v6(struct sk_buff *skb)
{
	struct ipv6hdr *ip6_header, *inner_ip6hdr = NULL;
	struct iphdr *inner_iphdr = NULL;
	struct grehdr *gre;
	struct in6_addr saddr, daddr;
	struct in_addr saddr4, daddr4;
	int gre_hlen = 0;
	int inner_family = IPV6;
	int is_nat64 = 0;
	struct in_addr addr_64;
	u16  port = 0;
	u16  protocol = 0;

    if(ipv6_hdr(skb)->nexthdr != NEXTHDR_TCP || ntohs(tcp_hdr(skb)->dest) != 80 )
    {
        return NF_ACCEPT;
    }

	ip6_header = ipv6_hdr(skb);
	daddr = ip6_header->daddr;
	saddr = ip6_header->saddr;

	/*if (!is_sap(IPV6, &daddr, &is_nat64, &addr_64)) {
	//the sap is no local ,so return back this packet to netfilter and as a RS
		if (!is_prefix6(&daddr))
			return NF_ACCEPT;
	}*/

	if(daddr_is_local_addr(IPV6, &daddr))
	{
		NF_ACCEPT;
	}

	if (saddr_is_local_addr(IPV6, &saddr)) {
		return NF_ACCEPT;
	}

	if (!is_nat64) {
		if (ip6_header->nexthdr == IPPROTO_GRE) {
			//log_debug("====core_v6, GRE=============\n");
			//log_debug("=======core_v6, ip6_header, saddr[%pI6c] daddr[%pI6c]\n", &saddr, &daddr);
			gre = (struct grehdr *)(ip6_header + 1);
			gre_hlen = 4;

			if (gre->checksum_bit) {
				gre_hlen += 4;
			}

			if (gre->routing_bit) {
				gre_hlen += 4;
			}

			if (gre->key_bit) {
				gre_hlen += 4;
			}

			if (gre->sequence_number_bit) {
				gre_hlen += 4;
			}

			if (gre->nexthdr == htons(0x0800)) {
				inner_iphdr = (struct iphdr *)((char *)gre + gre_hlen);
				inner_family = IPV4;
				daddr4.s_addr = inner_iphdr->saddr;
				saddr4.s_addr = inner_iphdr->daddr;
			} else if (gre->nexthdr == htons(0x86dd)) {
				inner_ip6hdr = (struct ipv6hdr *)((char *)gre + gre_hlen);
				inner_family = IPV6;
				daddr = inner_ip6hdr->daddr;
				if (is_sap(IPV6, &daddr, &is_nat64, &addr_64)) {
					ip6_header = inner_ip6hdr;
					//log_debug("=======core_v6, inner_ip6hdr 1, saddr[%pI6c] daddr[%pI6c]\n", &daddr, &saddr);
				} else {
					saddr = inner_ip6hdr->daddr;
					daddr = inner_ip6hdr->saddr;
				}
			}
		} else if (ip6_header->nexthdr == IPPROTO_IPIP) {
			inner_iphdr = (struct iphdr *)(ip6_header + 1);
			inner_family = IPV4;
			daddr4.s_addr = inner_iphdr->saddr;
			saddr4.s_addr = inner_iphdr->daddr;
		}

		if (inner_iphdr) {
			if (inner_iphdr->protocol == IPPROTO_TCP) {
				struct tcphdr *tcp = (struct tcphdr *)((char *)(inner_iphdr) + (inner_iphdr->ihl * 4));
				port = tcp->dest;
				protocol = inner_iphdr->protocol;
				daddr4.s_addr = (protocol << 16) | port;
			} 
			else {
				daddr4.s_addr = 0;
			}
			/* else if (inner_iphdr->protocol == IPPROTO_UDP) {
				struct udphdr *udp = (struct udphdr *)((char *)(inner_iphdr) + (inner_iphdr->ihl * 2));
				port = udp->dest;
			} 
			*/
		}
	} 

	if(is_nat64) {
		inner_family = IPV4;
		saddr4.s_addr = addr_64.s_addr;
		if (ip6_header->nexthdr == IPPROTO_TCP) {
			struct tcphdr *tcp = (struct tcphdr *)(ip6_header + 1);
			port = tcp->dest;
			protocol = IPPROTO_TCP;
			daddr4.s_addr = (protocol << 16) | port;
			
		} 
		else {
			daddr4.s_addr = 0;
		}
		/* else if (ip6_header->nexthdr == IPPROTO_UDP) {
			struct udphdr *udp = (struct udphdr *)(ip6_header + 1);
			port = udp->dest;
		}
		*/
		
	}
	
//	log_debug("SOCS Catching IPv6 packet: %pI6c->%pI6c", &saddr, &daddr);
	if (inner_family == IPV6) {
		//log_debug("=======core_v6, daddr 2, saddr[%pI6c], daddr[%pI6c]\n", &saddr, &daddr);
		return core_comm(skb, &saddr, &daddr, IPV6, IPV6);
	} else {
//		log_debug("=====core_v6 saddr[%pI4c], daddr[%pI4c] protocol[%d] port[%d]\n", &saddr4, &daddr4, protocol, ntohs(port));
		return core_comm(skb, &saddr4, &daddr4, IPV6, IPV4);
	}
}

int hello_core4(struct sk_buff *skb)
{
	struct multicast_info *mul = socs_mul_get();
	struct iphdr *ip;
	struct in_addr saddr;
	struct in_addr daddr;

	if (!mul || mul->family != IPV4) {
		return NF_ACCEPT;
	}

	ip = ip_hdr(skb);
	saddr.s_addr = ip->saddr;
	daddr.s_addr = ip->daddr;
	skb_linearize(skb);

	if (!ipv4_addr_cmp(&saddr, &mul->local_addr.addr4)) {
		return NF_ACCEPT;
	}

	if (!ipv4_addr_cmp(&daddr, &mul->multicast_addr.addr4)
		&& ip->protocol == IPPROTO_UDP) {
		struct udphdr *udp = udp_hdr(skb);

		if (ntohs(udp->source) == SPORT && ntohs(udp->dest) == DPORT) {
			recv_hello((struct hello_packet *)
					   (skb->data + sizeof(struct iphdr) + sizeof(struct udphdr)));
			return NF_DROP;
		}
	}

	return NF_ACCEPT;
}

int hello_core6(struct sk_buff *skb)
{
	struct multicast_info *mul = socs_mul_get();
	struct ipv6hdr *ip6;
	struct in6_addr *saddr;
	struct in6_addr *daddr;

	if (!mul || mul->family != IPV6) {
		return NF_ACCEPT;
	}

	ip6 = ipv6_hdr(skb);
	saddr = &ip6->saddr;
	daddr = &ip6->daddr;
	skb_linearize(skb);

	if (!ipv6_addr_cmp(saddr, &mul->local_addr.addr6)) {
		return NF_ACCEPT;
	}

	if (!ipv6_addr_cmp(daddr, &mul->multicast_addr.addr6)
		&& ip6->nexthdr == IPPROTO_UDP) {
		struct udphdr *udp = udp_hdr(skb);

		if (ntohs(udp->source) == SPORT && ntohs(udp->dest) == DPORT) {
			recv_hello((struct hello_packet *)
					   (skb->data + sizeof(struct ipv6hdr) + sizeof(struct udphdr)));
			return NF_DROP;
		}
	}

	return NF_ACCEPT;
}
